/* Copyright 2001,2002,2003 NAH6 BV
 * All Rights Reserved
 *
 *  $Header: /var/lib/cvs/secphone/ui/controls/UniButton.cpp,v 1.7 2003/11/21 12:39:24 itsme Exp $
 *
 *
 * handles graphical buttons, based on
 * http://www.codeguru.com/buttonctrl/UniButton.shtml
 *
 */

#include "stdafx.h"

#include "resource.h"
#include "UniButton.h"
#include "debug.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


#define DEBUGBUTTON  if (0) debug

// minimum time button needs to be down for detection.
#define MINIMUM_BUTTONDOWN_MSEC 200

/////////////////////////////////////////////////////////////////////////////
// CUniButton

CUniButton::CUniButton()
{
    m_nColor = GetSysColor(COLOR_BTNFACE);
    m_sColor = m_nColor;
    m_dColor = m_nColor;
    m_nBorder = 1;
    m_lfEscapement = 0;
    m_pNormal = NULL;
    m_pSelected = NULL;
    m_pChecked= NULL;
    m_pDisabled = NULL;
    m_hCursor= AfxGetApp()->LoadCursor( IDC_HAND );
    m_hRgn = 0;
    m_bHit = false;
    m_bCapture = false;
    m_bMouseDown = false;
    m_bNeedBitmaps = true;
    m_iCnt= 1;
    m_iIndex= 0;
    m_bPressed= false;
    m_bChecked= false;

    m_iRepCnt= 0;
    m_cChar= 0;
}

CUniButton::~CUniButton()
{
    delete m_pNormal;
    delete m_pSelected;
    delete m_pChecked;
    delete m_pDisabled;
    DeleteObject(m_hRgn);
}

BEGIN_MESSAGE_MAP(CUniButton, CButton)
    //{{AFX_MSG_MAP(CUniButton)
    ON_WM_ERASEBKGND()
    ON_WM_MOUSEMOVE()
    ON_WM_CREATE()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_WM_SETCURSOR()
    ON_CONTROL_REFLECT_EX(BN_CLICKED,OnClick)
    ON_WM_CHAR()
    ON_WM_TIMER()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CUniButton constructors

/*
BOOL CUniButton::Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID)
{
    // store region in member variable
    DeleteObject(m_hRgn);
    m_hRgn = CreateRectRgn(0, 0, 31, 31);
    CRect box(0, 0, 0, 0);
    if (m_hRgn != 0)
        CombineRgn(m_hRgn, hRgn, 0, RGN_COPY);

    // make sure that region bounding rect is located in (0, 0)
    GetRgnBox(m_hRgn, &box);
    OffsetRgn(m_hRgn, -box.left, -box.top);
    GetRgnBox(m_hRgn, &box);

    // update position of region center for caption output
    m_CenterPoint = CPoint(box.left + box.Width() /2 , box.top + box.Height() /2);
    box.OffsetRect(point);

    return CButton::Create(lpszCaption, dwStyle, box, pParentWnd, nID);
}

BOOL CUniButton::Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID, COLORREF color)
{
    m_sColor = color;

    // call another constructor
    return Create(lpszCaption, dwStyle, point, hRgn, pParentWnd, nID);
}

BOOL CUniButton::Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID, UINT nBorder, LONG lfEscapement, COLORREF nColor, COLORREF sColor, COLORREF dColor)
{
    m_lfEscapement = lfEscapement;
    return Create(lpszCaption, dwStyle, point, hRgn, pParentWnd, nID, nBorder, nColor, sColor, dColor);
}

BOOL CUniButton::Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID, UINT nBorder, COLORREF nColor, COLORREF sColor, COLORREF dColor)
{
    // change default colors
    m_nBorder = nBorder;
    m_nColor = nColor;
    m_sColor = sColor;
    m_dColor = dColor;

    // call another constructor
    return Create(lpszCaption, dwStyle, point, hRgn, pParentWnd, nID);
}
*/
/*
// this is the constructor used by the MakeKey function
BOOL CUniButton::Create(LPCTSTR lpszCaption, DWORD dwStyle, CDC *pDC, const CPoint point, 
                        CWnd* pParentWnd, UINT nID, 
                        UINT nBMask, UINT nBNormal, UINT nBSelected, UINT nBDisabled, 
                        UINT index, UINT cnt )
{
    m_iIndex= index;    // the index into the buttonbitmap
    m_iCnt= cnt;        // the number of buttons in the bitmap, but unused.
    m_dwStyle= dwStyle;

    delete m_pNormal;
    m_pNormal = new CBitmap;
    m_pNormal->LoadBitmap( nBNormal );

    delete m_pSelected;
    m_pSelected = new CBitmap;
    if( nBSelected )
        m_pSelected->LoadBitmap( nBSelected );
    else
        m_pSelected->LoadBitmap( nBNormal );

    delete m_pDisabled;
    m_pDisabled = new CBitmap;
    if( nBDisabled )
        m_pDisabled->LoadBitmap( nBDisabled );
    else
        m_pDisabled->LoadBitmap( nBNormal );

    m_bNeedBitmaps = false;

    if( nBMask )
    {
        // make region from Mask
        SetupRegion( pDC, nBMask );
    }
    else
    {
        // make region from Normal bitmap
        SetupSquareRegion( pDC, nBNormal, cnt );
    }
    CRect box(0, 0, 0, 0);
    GetRgnBox(m_hRgn, &box);

    box.OffsetRect(point);

    return CButton::Create(lpszCaption, WS_CHILD | (dwStyle&0xFFFFFF00L) | BS_OWNERDRAW , box, pParentWnd, nID);
}
*/

BOOL CUniButton::CreatePushButton(CDC *pDC, const RECT& pos, CWnd* pParentWnd, UINT nID, 
                        UINT nBMask, UINT nBNormal, UINT nBSelected, UINT nBDisabled)
{
    m_iIndex= 0;
    m_dwStyle= WS_VISIBLE | BS_PUSHBUTTON;

    delete m_pNormal;
    m_pNormal = new CBitmap;
    if (!m_pNormal->LoadBitmap( nBNormal ))
        return FALSE;

    delete m_pSelected;
    m_pSelected = new CBitmap;
    if (!m_pSelected->LoadBitmap( nBSelected ))
        return FALSE;

    delete m_pDisabled;
    m_pDisabled = new CBitmap;
    if (!m_pDisabled->LoadBitmap( nBDisabled )) {
        delete m_pDisabled;
        m_pDisabled= NULL;
    }

    m_bNeedBitmaps = false;

    // make region from Mask
    if (!nBMask || !SetupRegion( pDC, nBMask )) {
        // otherwise make region from Normal bitmap
        SetupSquareRegion( pDC, nBNormal, 1 );
    }
    CRect box(0, 0, 0, 0);
    GetRgnBox(m_hRgn, &box);

    box.OffsetRect(pos.left, pos.top);

    return CButton::Create(L"BUTTON", WS_CHILD | BS_OWNERDRAW | WS_VISIBLE | BS_PUSHBUTTON, box, pParentWnd, nID);
}
BOOL CUniButton::CreateCheckButton(CDC *pDC, const RECT& pos, CWnd* pParentWnd, UINT nID, 
                        UINT nBMask, UINT nBNormal, UINT nBSelected, UINT nBChecked, UINT nBDisabled)
{
    m_iIndex= 0;
    m_dwStyle= WS_VISIBLE | BS_PUSHBUTTON;

    delete m_pNormal;
    m_pNormal = new CBitmap;
    if (!m_pNormal->LoadBitmap( nBNormal ))
        return FALSE;

    delete m_pSelected;
    m_pSelected = new CBitmap;
    if (!m_pSelected->LoadBitmap( nBSelected ))
        return FALSE;

    delete m_pChecked;
    m_pChecked= new CBitmap;
    if (!m_pChecked->LoadBitmap( nBChecked ))
        return FALSE;

    delete m_pDisabled;
    m_pDisabled = new CBitmap;
    if (!m_pDisabled->LoadBitmap( nBDisabled )) {
        delete m_pDisabled;
        m_pDisabled= NULL;
    }

    m_bNeedBitmaps = false;

    // make region from Mask
    if (!nBMask || !SetupRegion( pDC, nBMask )) {
        // otherwise make region from Normal bitmap
        SetupSquareRegion( pDC, nBNormal, 1 );
    }
    CRect box(0, 0, 0, 0);
    GetRgnBox(m_hRgn, &box);

    box.OffsetRect(pos.left, pos.top);

    return CButton::Create(L"BUTTON", WS_CHILD | BS_OWNERDRAW | WS_VISIBLE | BS_PUSHBUTTON, box, pParentWnd, nID);
}


void CUniButton::SetupSquareRegion(CDC *pDC, unsigned short nBMask, UINT cnt )
{
    CBitmap         cBitmap;
    BITMAP bm;

    cBitmap.LoadBitmap(nBMask);
    GetObject(HBITMAP(cBitmap), sizeof(bm), &bm);

    m_hRgn= CreateRectRgn(0, 0, bm.bmWidth / cnt, bm.bmHeight);
}

BOOL CUniButton::SetupRegion(CDC *pDC, unsigned short nBMask)
{
    CDC             memDC;
    CBitmap         cBitmap;
    CBitmap*        pOldMemBmp = NULL;
    COLORREF        col;
    int             x, y;
    CRgn            *wndRgn, rgnTemp;

    if (!cBitmap.LoadBitmap(nBMask))
        return FALSE;

    BITMAP bm;
    GetObject(HBITMAP(cBitmap), sizeof(bm), &bm);

    memDC.CreateCompatibleDC(pDC);
    pOldMemBmp = memDC.SelectObject(&cBitmap);

    m_hRgn= CreateRectRgn(0, 0, bm.bmWidth, bm.bmHeight);
    wndRgn= CRgn::FromHandle( m_hRgn );
    BOOL bIsTrans;
    int startx;

    for(y=0; y<bm.bmHeight; y++)
    {
        for(x=0, bIsTrans= false; x<bm.bmWidth; x++)
        {
            col = memDC.GetPixel(x, y);
            if( col != 0 && !bIsTrans )
            {
                // start of region
                bIsTrans= true;
                startx= x;
            }
            if( col == 0 && bIsTrans )
            {
                // end of region
                bIsTrans= false;
                rgnTemp.CreateRectRgn(startx, y, x, y+1);
                wndRgn->CombineRgn(wndRgn, &rgnTemp, RGN_XOR);
                rgnTemp.DeleteObject();
            }
        }
        if( bIsTrans )
        {
            // end of region at end of line
            rgnTemp.CreateRectRgn(startx, y, x, y+1);
            wndRgn->CombineRgn(wndRgn, &rgnTemp, RGN_XOR);
            rgnTemp.DeleteObject();
        }
    }
    if (pOldMemBmp)
        memDC.SelectObject(pOldMemBmp);

    return TRUE;
}

void CUniButton::PreSubclassWindow()
{
    // change window style to allow owner draw
//  ModifyStyle(0, BS_OWNERDRAW | m_dwStyle);
    CButton::PreSubclassWindow();
}


/////////////////////////////////////////////////////////////////////////////
// CUniButton message handlers

int CUniButton::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CButton::OnCreate(lpCreateStruct) == -1)
        return -1;

    // assign new region to a window
#ifdef _WIN32_WCE
	::SetWindowRgn(m_hWnd, m_hRgn, true);
#else
    SetWindowRgn(m_hRgn, true);
#endif
    return 0;
}

void CUniButton::OnLButtonDown(UINT nFlags, CPoint point)
{
    DEBUGBUTTON("button[%c].lbuttondown\n", m_cChar?m_cChar:'@');
    if( m_cChar )
    {
        m_iRepCnt= 1;
        KillTimer( 666 );

        UINT nTimer= SetTimer( 666, MINIMUM_BUTTONDOWN_MSEC, NULL );
    }
    // record that mouse is down
    m_bMouseDown = true;
    if (!m_bCapture)
    {
        SetCapture();
        m_bHit= true;
        m_bCapture = true;
    }
    RedrawWindow();

    // call Default in stead of normal handler to disable gesture handling!
    CButton::OnLButtonDown(nFlags, point);
//  Default();
}

void CUniButton::OnLButtonUp(UINT nFlags, CPoint point)
{
    DEBUGBUTTON("button[%c].lbuttonup\n", m_cChar?m_cChar:'@');
    KillTimer( 666 );
    // record that mouse is released
    CButton::OnLButtonUp(nFlags, point);

    // generate chars too!
    if( m_cChar && HitTest(point) && m_iRepCnt<=1 )
        MakeChar();

    m_bMouseDown = false;
    if (m_bCapture)
    {
        ReleaseCapture();
        m_bCapture = false;
        m_bHit= false;
    }
    RedrawWindow();
}

void CUniButton::MakeChar()
{
    GetParent()->SendMessage(WM_CHAR, m_cChar, 0x80000000 | (DWORD)m_iRepCnt );
}

void CUniButton::OnMouseMove(UINT nFlags, CPoint point)
{
    if( m_bCapture )
    {
        // Test if mouse is above the button.
        if (HitTest(point))
        {
            if( !m_bHit )
            {
                //DEBUGBUTTON("mousemove[%c]: entering button\n", m_cChar?m_cChar:'@');
                m_bHit= true;
                RedrawWindow();
            }
            else {
                //DEBUGBUTTON("mousemove[%c]: reentering hit button\n", m_cChar?m_cChar:'@');
            }
        }
        else
        {
            if( m_bHit )
            {
                //DEBUGBUTTON("mousemove[%c]: leaving button\n", m_cChar?m_cChar:'@');
                KillTimer( 666 );
                m_bHit= false;
                //ReleaseCapture();
                //m_bCapture = false;

                RedrawWindow();
            }
            else {
                //DEBUGBUTTON("mousemove[%c]: leaving unhit button\n", m_cChar?m_cChar:'@');
            }
        }
    }
    else {
        //DEBUGBUTTON("mousemove[%c]: uncap: test=%d hit=%d\n", m_cChar, HitTest(point), m_bHit);
    }

    CButton::OnMouseMove(nFlags, point);
}

LRESULT CUniButton::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    // I have noticed that default windows buttons can be clicked very quickly.
    // Double or single click both result in a button being pushed down.
    // For owner drawn buttons this is not the case. Double click does
    // not push button down. Here is a solution for the problem:
    // double click message is substituted for single click.

    if (message == WM_LBUTTONDBLCLK)
        message = WM_LBUTTONDOWN;

    return CButton::DefWindowProc(message, wParam, lParam);
}

BOOL CUniButton::HitTest(CPoint point)
{
    BOOL result = false;

    // Obtain handle to window region.
    HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
    ::GetWindowRgn(m_hWnd, hRgn);
    CRect rgnRect;
    GetRgnBox(hRgn, &rgnRect);

    // First check if point is in region bounding rect.
    // Then check if point is in the region in adition to being in the bouding rect.
    result = PtInRect(&rgnRect, point) && PtInRegion(hRgn, point.x, point.y);

    // Clean up and exit.
    DeleteObject(hRgn);
    return result;
}

BOOL CUniButton::OnEraseBkgnd(CDC* pDC)
{
    // do not erase background
    return 1;
}

//////////////////////// DRAWING ROUTINES ////////////////////////////

void CUniButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    // prepare DC
    CDC* pDC = CDC::FromHandle(lpDrawItemStruct -> hDC);
    CRect rect;
    GetClientRect(rect);
/*
    // prepare bitmaps they need to be prepared
    if (m_bNeedBitmaps)
        PrepareStateBitmaps(pDC, &rect);
*/
    // draw button to the screen
    DrawButton(pDC, &rect, lpDrawItemStruct -> itemState);
}
/*
void CUniButton::PrepareStateBitmaps(CDC * pDC, CRect * pRect)
{
    // prepare memory DC
    CDC * pMemDC;
    pMemDC = new CDC;
    pMemDC -> CreateCompatibleDC(pDC);

    // prepare bitmaps for all button states and for the mask
    PrepareNormalState(pDC, pMemDC, pRect);
    PrepareSelectedState(pDC, pMemDC, pRect);
    PrepareDisabledState(pDC, pMemDC, pRect);

    // clean up
    delete pMemDC;
    m_bNeedBitmaps = false;
}


void CUniButton::PrepareNormalState(CDC * pDC, CDC * pMemDC, CRect * pRect)
{
    // prepare MYBS_NORMAL state bitmap
    delete m_pNormal;
    m_pNormal = new CBitmap;
    PaintRgn(pDC, pMemDC, m_pNormal, m_nColor, pRect, true, false);
}

void CUniButton::PrepareSelectedState(CDC * pDC, CDC * pMemDC, CRect * pRect)
{
    // prepare MYBS_SELECTED state bitmap
    delete m_pSelected;
    m_pSelected = new CBitmap;
    PaintRgn(pDC, pMemDC, m_pSelected, m_sColor, pRect, true, true);
}

void CUniButton::PrepareDisabledState(CDC * pDC, CDC * pMemDC, CRect * pRect)
{
    // prepare MYBS_DISABLED state bitmap
    delete m_pDisabled;
    m_pDisabled = new CBitmap;
    PaintRgn(pDC, pMemDC, m_pDisabled, m_dColor, pRect, false, false);
}
*/
void CUniButton::PaintRgn(CDC * pDC, CDC * pMemDC, CBitmap * pBitmap, COLORREF color, CRect * pRect, BOOL bEnabled, BOOL bSunken)
{
    // create bitmap
    pBitmap -> CreateCompatibleBitmap(pDC, pRect -> Width(), pRect -> Height());
    CBitmap * pOldBitmap = pMemDC -> SelectObject(pBitmap);

    // prepare region
    HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
    ::GetWindowRgn(m_hWnd, hRgn);

    // fill rect a with transparent color and fill rgn
    HBRUSH hBrush = CreateSolidBrush(color);
    pMemDC -> FillSolidRect(pRect, RGB(0, 0, 0));
    FillRgn(pMemDC -> GetSafeHdc(), hRgn, hBrush);
    DeleteObject(hBrush);

    // clean up
    DeleteObject(hRgn);
    pMemDC -> SelectObject(pOldBitmap);
}

void CUniButton::DrawButton(CDC * pDC, CRect * pRect, UINT state)
{
    // create memory DC
    CDC * pMemDC = new CDC;
    pMemDC -> CreateCompatibleDC(pDC);
    CBitmap * pOldBitmap;

    // get region
    HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
    ::GetWindowRgn(m_hWnd, hRgn);

    // select bitmap to paint depending upon button state
    if (state & ODS_DISABLED)
        pOldBitmap = pMemDC -> SelectObject(m_pDisabled);
    else {

//      DEBUGBUTTON("drawbutton[%c]: state=%d pressed=%d !cap&mouse=%d cap&hit=%d  cap=%d mous=%d hit=%d\n",
//          m_cChar?m_cChar:'@',
//          (state & ODS_SELECTED), m_bPressed,
//          (!m_bCapture && m_bMouseDown), (m_bCapture && m_bHit),
//          m_bCapture, m_bMouseDown, m_bHit);


        if ((state & ODS_SELECTED) 
            || m_bPressed
            || (!m_bCapture && m_bMouseDown) 
            || (m_bCapture && m_bHit) )
        {
            pOldBitmap = pMemDC -> SelectObject(m_pSelected);
        }
        else if (m_bChecked) {
            pOldBitmap = pMemDC -> SelectObject(m_pChecked);
        }       
        else
        {
            pOldBitmap = pMemDC -> SelectObject(m_pNormal);
        }
    }

    // paint using region for clipping
    ::SelectClipRgn(pDC -> GetSafeHdc(), hRgn);

    pDC -> BitBlt(0, 0, pRect -> Width(), pRect -> Height(), pMemDC, pRect->Width()*m_iIndex, 0, SRCCOPY);

    ::SelectClipRgn(pDC -> GetSafeHdc(), NULL);

    // clean up
    DeleteObject(hRgn);
    pMemDC -> SelectObject(pOldBitmap);
    delete pMemDC;
}

BOOL CUniButton::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    if (m_hCursor)
    {
        ::SetCursor(m_hCursor);
        return TRUE;
    }

    return CButton::OnSetCursor(pWnd, nHitTest, message);
}

BOOL CUniButton::OnClick( )
{
    DEBUGBUTTON("button[%c].onclick\n", m_cChar?m_cChar:'@');
    if( ( m_dwStyle & 0x1F )==BS_CHECKBOX )
    {
        m_bPressed= !m_bPressed;
        RedrawWindow();
    }
    else if( ( m_dwStyle & 0x1F )==BS_RADIOBUTTON )
    {
        m_bPressed= true;
        RedrawWindow();
    }
    return FALSE;
}

void CUniButton::SetCheck( bool bCheck )
{
    // if( ( ( m_dwStyle & 0x1F )==BS_CHECKBOX ) || ( ( m_dwStyle & 0x1F )==BS_RADIOBUTTON ))

    m_bChecked= bCheck;
    RedrawWindow();

    CButton::SetCheck( bCheck ? 1 : 0 );
}

bool CUniButton::GetCheck( )
{
    return m_bChecked;
}

// this message is received when the keyboard input is used.
// the most recently pressed button still has the focus, and
// thus gets the char message, we pass it on to the main dialog,
// where it is processed.
void CUniButton::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    DEBUGBUTTON("button[%c].onchar %02x - rep %d\n", m_cChar?m_cChar:'@', nChar, nRepCnt);
    GetParent()->SendMessage(WM_CHAR, nChar, nRepCnt);
}

void CUniButton::SetChar( char c )
{
    m_cChar= c;

    m_iRepCnt= 1;
}

void CUniButton::OnTimer( UINT nIDEvent )
{
    DEBUGBUTTON("button[%c].ontimer : %d\n", m_cChar?m_cChar:'@', m_iRepCnt);

    MakeChar( );
    m_iRepCnt++;

}
